不会产奶的COW(Copy-On-Write)

COW基本定义

写入时复制(Copy-on-write,简称COW)是一种计算机程序设计领域的优化策略。其核心思想是,如果有多个调用者(callers)同时要求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此作法主要的优点是如果调用者没有修改该资源,就不会有副本(private copy)被创建,因此多个调用者只是读取操作时可以共享同一份资源。

COW的处理过程中需要维持一个为读请求使用的“指针”,并在新数据写入完成后更新这个指针以提升读写并发能力。因此,COW也间接提供了数据更新过程中的原子性。在保证数据的完整性同时还保证了一定的读写效率,下面让我们针对一些采用COW的场景看看处理流程上的差异。

使用场景

在文件系统中的应用

数据的备份或者复制至少处于硬件(硬盘)和操作系统两个层级。硬件备份一般使用RAID0-10的方案,保护硬盘中的数据安全;操作系统中则使用Snapshot(快照)技术来维持数据的安全和有效。人们一直采用数据复制、备份、恢复等技术来保护重要的数据信 息,定期对数据进行备份或复制。由于数据备份过程会影响应用性能,并 且非常耗时,因此数据备份通常被安排在系统负载较轻时进行(如夜 间)。另外,为了节省存储空间,通常结合全量和增量备份技术。为了解决性能和持续运行的问题引入了Snapshot,Snapshot的实现又主要分为split mirror、changed block和current三大类,具体实现方式中会有部分依赖COW技术。

COW快照写操作基本流程:
1. 读取原始数据
2. 把原始数据复制到一块新的未被使用的快照空间
3. 把新数据写入到原始位置
写时复制快照

基本的处理流程比较简单,由于只复制了被更新部分数据,整个流程处理十分快速。在写操作的同时所有的读请求都是通过逻辑访问所有原始数据,包括被拷贝到新空间的部分,而不会读到正在写入的数据。

尽管COW简单高效,但是也存在一些问题,比如:
1. 数据复制过程带来了更多得io消耗(实际应用中可能更倾向于使用Redirect-on-write);
2. 快照只保存了被修改部分的数据,并没有完整的数据备份;
3. 如果新写入的数据超过旧数据占用的空间也会导致一系列复杂处理,最终导致快照失效;
4. 只能应对单一的数据处理,多重复杂结构数据的修改需要特殊处理

除了COW,常见的快照技术还有Log-structuredfilearchitecture(日志文件架构)、Copyonwritewithbackgroundcopy(克隆快照)、Continuous data protection(持续数据保护)Split Mirror(镜像分离)Pointer Remapping(指针重映射)等等,特性对比参见下表

镜像分离 指针重映射 写时复制 日志文件 克隆快照 持续数据保护
快照是否依赖源数据卷 NO 镜像包含完整的数据副本 YES 未变化的数据从源数据卷访问 YES 未变化数据从源数据卷访问 YES 未变化数据从源数据卷访问 ONLY 仅在后台拷贝未被完成时 YES 除了包含源数 据副本实现外
空间效率 NO 要求源数据卷相同容量存储空间 YES 大多数据情况下要求变化数据存储空间 YES 大多数据情况下要求变化数据存储空间 YES 要求变化数据存储空间 NO 要求源数据卷相同容量存储空间 YES 存储空间需求取决于保存变化数据的数据和频率
源数据卷系统CPU和I/O负载 LOW/HIGH 镜像分离后低,分离前数据同步高 HIGH/NONE软件型快照高,硬件型快照无 HIGH/NONE软件型快照高,硬件型快照无 HIGH对写操作进行日志时高 LOW一般由存储子硬件执行 具体实现相关
源数据卷写负载 NONE写负载发生在分离前 NONE写直接重定向至新块 HIGH首次写产生额外写负载 HIGH写操作必须进行日志 HIGH拷贝完成前的首次写产生写负载 HIGH每次写操作导致相应的写操作
逻辑数据错误保护机制 YES数据必须从镜像卷拷贝,变化没有记录,速度较慢 YES数据变化可以回滚或者同步至源数据源 YES数据变化可以回滚或者同步至源数据源 YES数据变化可以回滚 YES可以反向建立快照,由于仅复制变化数据块,速度较快 YES数据变化可以同步至原始数据副本
源数据卷物理介质故障保护机制 YES镜像卷是完整副本 NONE有效源数据卷必须存在 NONE有效源数据卷必须存在 NONE 有效源数据卷必须存在 YES后台复制完成后完全保护 具体实现相关

软件和web系统中也经常出现COW的使用场景,比如数据备份和并发环境下的读写性能力提升。

软件和web系统中的应用

linux中创建轻量级的子进程

我们都知道,进程是操作系统中比较昂贵的资源,它具有自己的数据和程序。

传统方式下,fork()函数在创建子进程时直接把所有资源复制给子进程,即:正文段块,数据段块,堆块,栈块。这种实现方式简单,但是效率低下,而且复制的资源可能对子进程毫无用处。linux为了降低创建子进程的成本,改进fork()实现方式使用COW技术创建子进程。当父进程创建子进程时,内核只为子进程创建虚拟空间,父子两个进程使用的是相同的物理空间。只有父子进程发生更改时才会为子进程分配独立的物理空间

fork()函数

上图展示从P1进程创建子进程P2时的物理空间使用状况。通过COW技术,fork()延迟了数据拷贝,根据子进程的实际操作最终可能完全避免数据复制,如:子进程创建后运行一个与当前数据无关的可执行文件。

redis中的COW

某些情况下,我们希望保存redis中的数据,那么只用一个主线程处理请求的redis是怎么在处理请求的同时进行数据持久化的呢?答案当然是依赖COW,具体来说就是依赖系统的fork()函数的COW实现。

redis有两种数据持久化策略:RDB快照和AOF日志。

RDB快照复制某一时刻redis内存中的所有数据保存在文件中,在数据备份程序被触发后redis会调用fork(),这样在轻松获取一份该时刻数据副本的同时,还可以允许主进程继续接受其它请求,读请求不会增加负担,只有写请求到达时才需要真正复制一份数据。RDB快照的一大缺点是可能丢失两次备份时刻之间产生的数据。

AOF日志弥补了RDB快照的不足,将每个收到的写命令都写入日志文件保证数据不会丢失。日志文件会随时间不停增长,为了解决过大的日志文件,redis提供了bgrewriteaof命令对日志进行压缩。bgrewriteaof命令也会调用fork()函数,利用子进程中的数据状态转化成redis命令并保存到新的aof日志中,之后同步父进程中缓存的新写命令,最后用新日志文件替换旧日志文件

可见,两种数据持久化策略都使用了COW。

jdk中的COW集合

java的基础api中也提供了基于COW的数据集合,但是与前面进行数据备份和降低数据复制开销的目的不同,java中的COW集合更偏向于提供并发能力。

CopyOnWriteArrayListCopyOnWriteArraySet都是使用在读多写少且数据总量不大的场景下,在保证多线程写的原子性的同时又避免了读的冲突和竞争,使用迭代器的时候也绝对不会抛出ConcurrentModificationException。

在集合内部,利用ReentrantLock同步多个写操作,锁竞争胜利进入同步代码段的程序经过已下几步完成数据修改:

1.复制原始数据
2.修改原始数据
3.将修改后的数据赋给集合数据引用
4.退出同步锁定区域

在写方法执行前和执行中的读操作都是直接取得旧数据的引用,由于迭代器的修改方法被禁用,所以迭代遍历数据的程序总是在使用一份不会改变的数据引用。因为写操作最后修改数据引用的操作是原子的,所以读操作不会错误的得到“部分修改”的数据。

linux内核中有一种叫做RCU(Read-Copy Update)数据共享策略跟COW十分相似,唯一不同在于RCU的新旧数据替换同过特定的回调(callback)机制被动执行,而不是由写操作进程自助完成,有兴趣的同学可以自己研究一下。

总结

copy-on-write是个古老,容易理解且比较高效的策略,可以在数据备份或者读多写少的场景下选择使用。同时它也存在一些先天缺点,数据复制过程中需要双倍的存储空间,如果涉及到IO读取那还会成倍消耗IO资源,使用过程中需要注意控制copy的范围。

有兴趣的同学可以自行阅读相关技术,扩展学习一下优化策略。如:Redirect-On-Write、Copy-On-First-Write、Allocate-On-Flush(also called delayed allocation)、RCU。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容